package com.talkyun.openx.client;

import com.talkyun.openx.RestfulHeaderManager;
import com.talkyun.openx.client.codec.JsonCodec;
import com.talkyun.openx.client.conn.HttpConnector;
import com.talkyun.openx.client.conn.HttpConnectorFactory;
import com.talkyun.openx.client.conn.HttpNotConnectionException;
import com.talkyun.openx.client.conn.HttpResult;
import com.talkyun.openx.error.ExceptionHandler;
import com.talkyun.utils.json.JSON;
import com.talkyun.utils.para.ParaNamer;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;

import static com.talkyun.openx.RestfulHeaderManager.get;

public class RestfulJsonProxy extends AbstractProxy {
    private static final HttpConnectorFactory factory = new HttpConnectorFactory();
    private static final RestfulHeaderManager headerManager = RestfulHeaderManager.instance();
    private static final ExceptionHandler handler = new ExceptionHandler();
    private static final ParaNamer namer = new ParaNamer();
    private static final JsonCodec codec = new JsonCodec();

    protected Class<?> api;

    public RestfulJsonProxy(String url, String mapping, int timeout) {
        this.mapping = mapping.startsWith("/") ? mapping : "/" + mapping;
        if (url == null || url.trim().length() == 0) {
            throw new RuntimeException("Service url is null!");
        }

        setUrl(url.endsWith("/") ? url.substring(0, url.length() - 1) : url);
        setTimeout(timeout);
    }

    protected RestfulJsonProxy() {

    }

    public void setApiClass(Class<?> api) {
        this.api = api;
    }

    public Class<?> getApiClass() {
        return api;
    }

    @Override
    public String toString() {
        return api == null ? "null" : api.getName() + "(dc:" + url + ")";
    }

    @Override
    protected Object doInvoke(Object proxy, Method method, Object[] args) throws Exception {
        String url2 = url + mapping + "/" + method.getName();
        return doInvoke(url2, proxy, method, args);
    }

    protected Object doInvoke(String url, Object proxy, Method method, Object[] args) throws Exception {
        String json = this.toJsonString(method, args);
        HttpResult result = invokeSync(url, json, timeout);

        if (result == null) {
            throw new RuntimeException("Call remote service timeout!");
        } else if (result.getStatus() != 200) {
            throw handler.decode(result.getResult());
        } else {
            return codec.decode(method.getGenericReturnType(), result.getResult());
        }
    }

    private HttpResult invokeSync(final String url, final String json, final int timeout) {
        try {
            return this.doHttpInvoke(url, timeout, json);
        } catch (HttpNotConnectionException e) {
            throw e;
        } catch (Throwable t) {
            return new HttpResult(500, t.getClass().getName());
        }
    }

    private HttpResult doHttpInvoke(String url, int timeout, String body) throws IOException {
        HttpConnector conn = getHttpConnector(url, timeout);

        Map<String, String> header = get().getOnceHead();
        return conn.sendWithHeader("POST", header, body);
    }

    protected String toJsonString(Object obj) {
        return JSON.toJsonString(obj);
    }

    private String toJsonString(Method method, Object[] args) {
        // init buffer and add first '{'
        StringBuilder json = new StringBuilder(96).append("{");

        // para name/value : para value pair
        List<ParaNamer.Param> list = namer.getParaList(method);
        if (!list.isEmpty()) {
            for (int i = 0; i < args.length; i++) {
                ParaNamer.Param para = list.get(i);
                if (i != 0) {
                    json.append(", ");
                }
                json.append('"').append(para.getName()).append('"');
                json.append(":");
                json.append(JSON.toJsonString(args[i]));
            }
        }

        // add head: _openx_head, FIX now use once-head
        // Map<String, String> head = headerManager.getHead();
        Map<String, String> head = headerManager.getOnceHead();
        if (head != null && !head.isEmpty()) {
            if (json.length() > 1) {
                json.append(',');
            }
            json.append("\"_openx_head\":");
            json.append(JSON.toJsonString(head));
        }

        // add last '}', return
        return json.append("}").toString();
    }

    private HttpConnector getHttpConnector(String url, int timeout) {
        if (httpProxyAddress == null || httpProxyAddress.trim().length() == 0) {
            return factory.getConnector(url, timeout);
        }

        return factory.getConnector(url, timeout, httpProxyAddress, httpProxyAuthorization);
    }
}